import { __private, _decorator, clamp, Enum, InstanceMaterialType, Material, NodeEventType, RenderTexture, Sprite, SpriteAtlas, SpriteFrame, UIRenderer, UITransform, Vec2 } from 'cc';
import { BUILD, EDITOR } from 'cc/env';
const { ccclass, property, menu, executionOrder, requireComponent } = _decorator;

enum FixType {
  NORMAL,
  WIDTH,
  HEIGHT,
  All,
}

@ccclass('SpriteCopy')
@menu('myComponent/SpriteCopy')
@executionOrder(110)
@requireComponent(UITransform)
export class SpriteCopy extends UIRenderer {
  /**
   * @en
   * The sprite atlas where the sprite is.
   *
   * @zh
   * 精灵的图集。
   */
  @property({
    type: SpriteAtlas,
    displayOrder: 4,
    tooltip: '图片资源所属的Atlas图集资源',
  })
  get spriteAtlas(): SpriteAtlas | null {
    return this._atlas;
  }
  set spriteAtlas(value) {
    if (this._atlas === value) {
      return;
    }
    this._atlas = value;
  }

  /**
   * @en
   * The sprite frame of the sprite.
   *
   * @zh
   * 精灵的精灵帧。
   */
  @property({
    type: SpriteFrame,
    displayOrder: 5,
    tooltip: '渲染Sprite使用的Sprite Frame图片资源',
  })
  get spriteFrame(): SpriteFrame | null {
    return this._spriteFrame;
  }
  set spriteFrame(value) {
    if (this._spriteFrame === value) {
      return;
    }

    const lastSprite = this._spriteFrame;
    this._spriteFrame = value;
    this.markForUpdateRenderData();
    this._applySpriteFrame(lastSprite);
    if (EDITOR) {
      this.node.emit(Sprite.EventType.SPRITE_FRAME_CHANGED, this);
    }

    if (value) {
      const size = value.originalSize;
      if (this.sizeMode == Sprite.SizeMode.CUSTOM) {
        this.onSizeChangeSize(size.width, size.height);
      } else {
        this.onSizeChangeScale(size.width, size.height);
      }
    }
  }

  /**
   * @en
   * The sprite render type.
   *
   * @zh
   * 精灵渲染类型。
   *
   * @example
   * ```ts
   * import { Sprite } from 'cc';
   * sprite.type = Sprite.Type.SIMPLE;
   * ```
   */
  @property({
    type: Enum(Sprite.Type),
    displayOrder: 6,
    tooltip: '洎染模式:\n普通(Simple):修改尺寸会整体拉伸图像，适用于序列帧动画和普通图像\n-九宫格 Sliced 修改尺寸时四个角的区域不会拉伸，适用于UI按钮和面板背景\n平铺 Tiled 修改尺寸时会不断平铺原始大小的图片\n填充 Filled 设置定的填充起始位置和方向，能够以一定比率剪裁显示图片',
  })
  get type(): __private._cocos_2d_components_sprite__SpriteType {
    return this._type;
  }
  set type(value: __private._cocos_2d_components_sprite__SpriteType) {
    if (this._type !== value) {
      this._type = value;
      this._flushAssembler();
    }
  }

  /**
   * @en
   * The fill type, This will only have any effect if the "type" is set to “Sprite.Type.FILLED”.
   *
   * @zh
   * 精灵填充类型，仅渲染类型设置为 Sprite.Type.FILLED 时有效。
   *
   * @example
   * ```ts
   * import { Sprite } from 'cc';
   * sprite.fillType = Sprite.FillType.HORIZONTAL;
   * ```
   */
  @property({
    displayOrder: 6,
    tooltip: '填充方向，可以选择横向Horizontal，纵向 Vertical和扇形Radial三种方向',
    visible() {
      return this._type == Sprite.Type.FILLED;
    },
  })
  get fillType(): number {
    return this._fillType;
  }
  set fillType(value: number) {
    if (this._fillType !== value) {
      if (value === Sprite.FillType.RADIAL || this._fillType === Sprite.FillType.RADIAL) {
        this.destroyRenderData();
      } else if (this.renderData) {
        this.markForUpdateRenderData(true);
      }
    }

    this._fillType = value;
    this._flushAssembler();
  }

  /**
   * @en
   * The fill Center, This will only have any effect if the "type" is set to “Sprite.Type.FILLED”.
   *
   * @zh
   * 填充中心点，仅渲染类型设置为 Sprite.Type.FILLED 时有效。
   *
   * @example
   * ```ts
   * import { Vec2 } from 'cc';
   * sprite.fillCenter = new Vec2(0, 0);
   * ```
   */
  @property({
    type: Vec2,
    displayOrder: 6,
    tooltip: '扇形填充时，指定扇形的中心点，取值范围0~1',
    visible() {
      return this._type == Sprite.Type.FILLED;
    },
  })
  get fillCenter(): Vec2 {
    return this._fillCenter;
  }
  set fillCenter(value) {
    this._fillCenter.x = value.x;
    this._fillCenter.y = value.y;
    if (this._type === Sprite.Type.FILLED && this.renderData) {
      this.markForUpdateRenderData();
    }
  }

  /**
   * @en
   * The fill Start, This will only have any effect if the "type" is set to “Sprite.Type.FILLED”.
   *
   * @zh
   * 填充起始点，仅渲染类型设置为 Sprite.Type.FILLED 时有效。
   *
   * @example
   * ```ts
   * // -1 To 1 between the numbers
   * sprite.fillStart = 0.5;
   * ```
   */
  @property({
    range: [0, 1, 0.1],
    displayOrder: 6,
    tooltip: '填充起始位置，输入一个0~1之间的小数表示起始位置的百分比',
    visible() {
      return this._type == Sprite.Type.FILLED;
    },
  })
  get fillStart(): number {
    return this._fillStart;
  }

  set fillStart(value) {
    this._fillStart = clamp(value, 0, 1);
    if (this._type === Sprite.Type.FILLED && this.renderData) {
      this.markForUpdateRenderData();
      this._updateUVs();
    }
  }

  /**
   * @en
   * The fill Range, This will only have any effect if the "type" is set to “Sprite.Type.FILLED”.
   *
   * @zh
   * 填充范围，仅渲染类型设置为 Sprite.Type.FILLED 时有效。
   *
   * @example
   * ```ts
   * // -1 To 1 between the numbers
   * sprite.fillRange = 1;
   * ```
   */
  @property({
    range: [-1, 1, 0.1],
    displayOrder: 6,
    tooltip: '填充总量，取值范围0~1指定显示图像范围的百分比',
    visible() {
      return this._type == Sprite.Type.FILLED;
    },
  })
  get fillRange(): number {
    return this._fillRange;
  }
  set fillRange(value) {
    // positive: counterclockwise, negative: clockwise
    this._fillRange = clamp(value, -1, 1);
    if (this._type === Sprite.Type.FILLED && this.renderData) {
      this.markForUpdateRenderData();
      this._updateUVs();
    }
  }
  /**
   * @en
   * specify the frame is trimmed or not.
   *
   * @zh
   * 是否使用裁剪模式。
   *
   * @example
   * ```ts
   * sprite.trim = true;
   * ```
   */
  @property({
    displayOrder: 8,
    tooltip: '节点约束框内是否包括透明像素区域，勾选此项会去除节点约束框内的透明区域',
    visible() {
      return this._type == Sprite.Type.SIMPLE;
    },
  })
  get trim(): boolean {
    return this._isTrimmedMode;
  }

  set trim(value) {
    if (this._isTrimmedMode === value) {
      return;
    }

    this._isTrimmedMode = value;
    if (this._type === Sprite.Type.SIMPLE /* || this._type === SpriteType.MESH */ && this.renderData) {
      this.markForUpdateRenderData(true);
    }
  }

  /**
   * @en Grayscale mode.
   * @zh 是否以灰度模式渲染。
   */
  @property({
    displayOrder: 5,
    tooltip: '是否开启灰度渲染模式',
  })
  get grayscale(): boolean {
    return this._useGrayscale;
  }
  set grayscale(value) {
    if (this._useGrayscale === value) {
      return;
    }
    this._useGrayscale = value;
    this.changeMaterialForDefine();
    this.updateMaterial();
  }

  private updateMaterial() {
    if (this._customMaterial) {
      if (this.getSharedMaterial(0) !== this._customMaterial) {
        this.setSharedMaterial(this._customMaterial, 0);
      }
      return;
    }
    const mat = this._updateBuiltinMaterial();
    this.setSharedMaterial(mat, 0);
    if (this.stencilStage == 2 || this.stencilStage == 6) {
      this.getMaterialInstance(0)!.recompileShaders({ USE_ALPHA_TEST: true });
    }
    this._updateBlendFunc();
  }

  /**
   * @en
   * Specify the size tracing mode.
   *
   * @zh
   * 精灵尺寸调整模式。
   *
   * @example
   * ```ts
   * import { Sprite } from 'cc';
   * sprite.sizeMode = Sprite.SizeMode.CUSTOM;
   * ```
   */
  @property({
    type: Enum(Sprite.SizeMode),
    displayOrder: 5,
    tooltip: '指定Sprite所在节点的尺寸\nCUSTOM表示自定义尺寸\nTRIMMED表示取原始图片剪裁透明像素后的尺寸\nRAW表示取原始图片未剪裁的尺寸',
  })
  get sizeMode(): __private._cocos_2d_components_sprite__SizeMode {
    return this._sizeMode;
  }
  set sizeMode(value) {
    if (this._sizeMode === value) {
      return;
    }

    this._sizeMode = value;
    if (value !== Sprite.SizeMode.CUSTOM) {
      this._applySpriteSize();
    }
  }

  @property({ serializable: true })
  protected _spriteFrame: SpriteFrame | null = null;
  @property({ serializable: true })
  protected _type = Sprite.Type.SIMPLE;
  @property({ serializable: true })
  protected _fillType = Sprite.FillType.HORIZONTAL;
  @property({ serializable: true })
  protected _sizeMode = Sprite.SizeMode.TRIMMED;
  @property({ serializable: true })
  protected _fillCenter: Vec2 = new Vec2(0, 0);
  @property({ serializable: true })
  protected _fillStart = 0;
  @property({ serializable: true })
  protected _fillRange = 0;
  @property({ serializable: true })
  protected _isTrimmedMode = true;
  @property({ serializable: true })
  protected _useGrayscale = false;
  @property({ serializable: true })
  protected _atlas: SpriteAtlas | null = null;

  public __preload(): void {
    this.changeMaterialForDefine();
    super.__preload();

    if (EDITOR) {
      this._resized();
      this.node.on(NodeEventType.SIZE_CHANGED, this._resized, this);
    }
  }

  public onEnable(): void {
    super.onEnable();

    this._activateMaterial();
    const spriteFrame = this._spriteFrame;
    if (spriteFrame) {
      this._updateUVs();
      if (this._type === Sprite.Type.SLICED) {
        spriteFrame.on(SpriteFrame.EVENT_UV_UPDATED, this._updateUVs, this);
      }
    }
  }

  public onDisable(): void {
    super.onDisable();
    if (this._spriteFrame && this._type === Sprite.Type.SLICED) {
      this._spriteFrame.off(SpriteFrame.EVENT_UV_UPDATED, this._updateUVs, this);
    }
  }

  public onDestroy(): void {
    if (EDITOR) {
      this.node.off(NodeEventType.SIZE_CHANGED, this._resized, this);
    }
    super.onDestroy();
  }

  /**
   * @en
   * Quickly switch to other sprite frame in the sprite atlas.
   * If there is no atlas, the switch fails.
   *
   * @zh
   * 选取使用精灵图集中的其他精灵。
   * @param name @en Name of the spriteFrame to switch. @zh 要切换的 spriteFrame 名字。
   */
  public changeSpriteFrameFromAtlas(name: string): void {
    if (!this._atlas) {
      console.warn('SpriteAtlas is null.');
      return;
    }
    const sprite = this._atlas.getSpriteFrame(name);
    this.spriteFrame = sprite;
  }

  /**
   * @deprecated Since v3.7.0, this is an engine private interface that will be removed in the future.
   */
  public changeMaterialForDefine(): void {
    const lastInstanceMaterialType = this._instanceMaterialType;

    if (this.grayscale) {
      this._instanceMaterialType = InstanceMaterialType.GRAYSCALE;
    } else {
      this._instanceMaterialType = InstanceMaterialType.ADD_COLOR_AND_TEXTURE;
    }
    if (lastInstanceMaterialType !== this._instanceMaterialType) {
      this.updateMaterial();
    }
  }

  protected _updateBuiltinMaterial(): Material {
    let mat = super._updateBuiltinMaterial();
    if (this.spriteFrame && this.spriteFrame.texture instanceof RenderTexture) {
      const defines = { SAMPLE_FROM_RT: true, ...mat.passes[0].defines };
      const renderMat = new Material();
      renderMat.initialize({
        effectAsset: mat.effectAsset,
        defines,
      });
      mat = renderMat;
    }
    return mat;
  }

  protected _render(render): void {
    render.commitComp(this, this.renderData, this._spriteFrame, this._assembler, null);
  }

  protected _canRender(): boolean {
    if (!super._canRender()) {
      return false;
    }

    const spriteFrame = this._spriteFrame;
    if (!spriteFrame || !spriteFrame.texture) {
      return false;
    }

    return true;
  }

  protected _flushAssembler(): void {
    const assembler = Sprite.Assembler.getAssembler(this);

    if (this._assembler !== assembler) {
      this.destroyRenderData();
      this._assembler = assembler;
    }

    if (!this._renderData) {
      if (this._assembler && this._assembler.createData) {
        this._renderData = this._assembler.createData(this);
        this._renderData!.material = this.getRenderMaterial(0);
        this.markForUpdateRenderData();
        if (this.spriteFrame) {
          this._assembler.updateUVs(this);
        }
        this._updateColor();
      }
    }

    // Only Sliced type need update uv when sprite frame insets changed
    if (this._spriteFrame) {
      if (this._type === Sprite.Type.SLICED) {
        this._spriteFrame.on(SpriteFrame.EVENT_UV_UPDATED, this._updateUVs, this);
      } else {
        this._spriteFrame.off(SpriteFrame.EVENT_UV_UPDATED, this._updateUVs, this);
      }
    }
  }

  private _applySpriteSize(): void {
    if (this._spriteFrame) {
      if (BUILD || !this._spriteFrame.isDefault) {
        if (Sprite.SizeMode.RAW === this._sizeMode) {
          const size = this._spriteFrame.originalSize;
          this.node._uiProps.uiTransformComp!.setContentSize(size);
        } else if (Sprite.SizeMode.TRIMMED === this._sizeMode) {
          const rect = this._spriteFrame.rect;
          this.node._uiProps.uiTransformComp!.setContentSize(rect.width, rect.height);
        }
      }
    }
  }

  private _resized(): void {
    if (!EDITOR) {
      return;
    }

    if (this._spriteFrame) {
      const actualSize = this.node._uiProps.uiTransformComp!.contentSize;
      let expectedW = actualSize.width;
      let expectedH = actualSize.height;
      if (this._sizeMode === Sprite.SizeMode.RAW) {
        const size = this._spriteFrame.originalSize;
        expectedW = size.width;
        expectedH = size.height;
      } else if (this._sizeMode === Sprite.SizeMode.TRIMMED) {
        const rect = this._spriteFrame.rect;
        expectedW = rect.width;
        expectedH = rect.height;
      }

      if (expectedW !== actualSize.width || expectedH !== actualSize.height) {
        this._sizeMode = Sprite.SizeMode.CUSTOM;
      }
    }
  }

  private _activateMaterial(): void {
    const spriteFrame = this._spriteFrame;
    const material = this.getRenderMaterial(0);
    if (spriteFrame) {
      if (material) {
        this.markForUpdateRenderData();
      }
    }

    if (this.renderData) {
      this.renderData.material = material;
    }
  }

  private _updateUVs(): void {
    if (this._assembler) {
      this._assembler.updateUVs(this);
    }
  }

  private _applySpriteFrame(oldFrame: SpriteFrame | null): void {
    const spriteFrame = this._spriteFrame;

    if (oldFrame && this._type === Sprite.Type.SLICED) {
      oldFrame.off(SpriteFrame.EVENT_UV_UPDATED, this._updateUVs, this);
    }

    let textureChanged = false;
    if (spriteFrame) {
      if (!oldFrame || oldFrame.texture !== spriteFrame.texture) {
        textureChanged = true;
      }
      if (textureChanged) {
        if (this.renderData) this.renderData.textureDirty = true;
        this.changeMaterialForDefine();
      }
      this._applySpriteSize();
      if (this._type === Sprite.Type.SLICED) {
        spriteFrame.on(SpriteFrame.EVENT_UV_UPDATED, this._updateUVs, this);
      }
    }
  }

  @property({ type: Enum(FixType) })
  fixType: FixType = FixType.NORMAL;
  @property({
    visible() {
      return this.fixType == FixType.WIDTH || this.fixType == FixType.All;
    },
  })
  fixWidth: number = 20;
  @property({
    visible() {
      return this.fixType == FixType.HEIGHT || this.fixType == FixType.All;
    },
  })
  fixHeight: number = 20;
  @property({
    tooltip: '自适应是否放大图标',
    visible() {
      return this.fixType != FixType.NORMAL;
    },
  })
  enlargeIcon: boolean = false;

  onSizeChangeScale(ww: number, hh: number) {
    let ss = 1;
    switch (this.fixType) {
      case FixType.WIDTH:
        ss = this.fixWidth / ww;
        break;
      case FixType.HEIGHT:
        ss = this.fixHeight / hh;
        break;
      case FixType.All:
        ss = Math.min(this.fixWidth / ww, this.fixHeight / hh);
        break;
    }
    if (ss > 1) {
      if (this.enlargeIcon) {
        this.node.setScale(ss, ss);
      } else {
        this.node.setScale(1, 1);
      }
    } else {
      this.node.setScale(ss, ss);
    }
  }

  onSizeChangeSize(ww: number, hh: number) {
    const form = this.node.getComponent(UITransform);
    switch (this.fixType) {
      case FixType.WIDTH:
        if (this.fixWidth < ww || this.enlargeIcon) {
          const ss = this.fixWidth / ww;
          form.width = this.fixWidth;
          form.height = hh * ss;
        } else {
          form.width = ww;
          form.height = hh;
        }
        break;
      case FixType.HEIGHT:
        if (this.fixHeight < hh || this.enlargeIcon) {
          const ss = this.fixHeight / hh;
          form.height = this.fixHeight;
          form.width = ww * ss;
        } else {
          form.width = ww;
          form.height = hh;
        }
        break;
      case FixType.All:
        if (this.fixWidth > ww && this.fixHeight > hh && !this.enlargeIcon) {
          form.width = ww;
          form.height = hh;
        } else {
          const ss1 = this.fixWidth / ww;
          const ss2 = this.fixHeight / hh;
          if (ss1 < ss2) {
            form.width = this.fixWidth;
            form.height = hh * ss1;
          } else {
            form.width = ww * ss2;
            form.height = this.fixHeight;
          }
        }
        break;
    }
  }
}
